package com.xl.rentkeeper.net;

import android.os.Handler;
import android.text.TextUtils;

import com.squareup.wire.Wire;
import com.xl.fuse.protos.packet.Packet;
import com.xl.fuse.protos.packet.RetCode;
import com.xl.rentkeeper.App;
import com.xl.rentkeeper.business.UserLogic;
import com.xl.rentkeeper.log.QLog;
import com.xl.rentkeeper.mgr.ThreadManager;
import com.xl.rentkeeper.util.SLog;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by zjm on 2014/11/8.
 */
public class NetEngine implements IDUCallback {
    String serverIp = "180.150.186.187";
    int port = 11000;
    Wire wire = App.getWire();
    List<Request> pendingReqs = new LinkedList<Request>();
    //
    Map<Integer, Request> waitForResps = new ConcurrentHashMap<>();
    DUTransfer duTransfer;
    Handler handler = new Handler(ThreadManager.getInstance().getWorkerLooper());
    int mContinueFail = 0;
    int ContinueFailLimit = 5;
    static NetEngine sEngine;
    IPushCallback pushCallback;
    SLog log = new SLog(NetworkManager.tag+"."+this.getClass().getSimpleName(),true,true);
    boolean isShutdown = false;

    private static final String PARAM_NET_IP = "param_net_ip";
    private static final String PARAM_NET_Port = "param_net_port";

    public static NetEngine getInstance() {
        if (sEngine == null) {
            synchronized (NetEngine.class) {
                if (sEngine == null) {
                    sEngine = new NetEngine();
                }
            }
        }
        return sEngine;
    }

    public NetEngine() {
        serverIp = App.getApp().getSp().getString(PARAM_NET_IP, serverIp);
        port = App.getApp().getSp().getInt(PARAM_NET_Port,port);
        handler.post(reConnect);
    }

    public String getIp() {
        return App.getApp().getSp().getString(PARAM_NET_IP, serverIp)+":"+App.getApp().getSp().getInt(PARAM_NET_Port, port);
    }

    public void setIp(String ip) {
        if (!TextUtils.isEmpty(ip) && !ip.equals(serverIp)) {
            String[] adds = ip.split(":");
            this.serverIp = adds[0];
            this.port = 10001;
            if (adds.length == 2) {
                try {
                    this.port = Integer.parseInt(adds[1]);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                    this.port = 10001;
                }
            }
            App.getApp().getSp().edit().putString(PARAM_NET_IP, this.serverIp).commit();
            App.getApp().getSp().edit().putInt(PARAM_NET_Port, this.port).commit();
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (duTransfer != null) {
                        duTransfer.onError(new RuntimeException("set net ip " + serverIp+" port:"+port));
                    }
                }
            });
        }
    }

    // 用户登出后清理
    public void shutdown() {
        isShutdown = true;
        handler.removeCallbacksAndMessages(null);
        waitForResps.clear();
        pendingReqs.clear();
        if (duTransfer != null) {
            duTransfer.onError(new RuntimeException("shutdown"));
        }
    }

    public void start() {
        isShutdown = false;
        handler.removeCallbacks(reConnect);
        handler.post(reConnect);
    }


    public void setPushCallback(IPushCallback pushCallback) {
        this.pushCallback = pushCallback;
    }

    void dumpState(String s) {
        log.log(s + " pending:" + pendingReqs.size() + " waitForResps:" + waitForResps.size());
    }

    public synchronized void sendReq(Request req) {
        sendReq(req, false);
    }

    public synchronized void sendReq(Request req, boolean addToFirst) {
        if (req == null) {
            return;
        }
        mContinueFail = 0; // 如果业务有需求，都去尝试连接
        req.queueTs = System.currentTimeMillis();
        if (addToFirst)
            pendingReqs.add(0, req);
        else
            pendingReqs.add(req);
        setTimeout(req);
        // TODO 发送也放在工作线程可能某个回包长时间处理导致工作线程会有问题
        handler.post(new Runnable() {
            @Override
            public void run() {
                trySendingOne();
            }
        });
    }

    void setTimeout(Request req) {
        Runnable timeout = new TimeoutRunnable(req);
        req.timeOutRun = timeout;
        handler.postDelayed(timeout, req.timeOut);
    }

    class TimeoutRunnable implements Runnable {
        Request req;

        public TimeoutRunnable(Request req) {
            this.req = req;
        }

        @Override
        public void run() {
            if (req.callback != null) {
                Packet resp = req.getTimeoutPacket();
                log.log("recv " + Request.dumpPacket(resp));
                pendingReqs.remove(req);
                req.callback.onResp(req, req.getTimeoutPacket());
            }
        }
    }

    void trySendingOne() {
        log.log("trySendingOne " + duTransfer + " callback:" + (duTransfer != null ? duTransfer.callback : null)+" pendingSize:"+pendingReqs.size());
        if (pendingReqs.size() > 0) {
            if (duTransfer == null) {
                handler.removeCallbacks(reConnect);
                handler.post(reConnect);
                return;
            }
            Request req = pendingReqs.get(0);
            if (duTransfer != null && duTransfer.send(req.packet)) {
                pendingReqs.remove(0);
                log.log("send " + req + " body:" + (req.oriPb != null ? req.oriPb.toString() : "null"));
                waitForResps.put(req.packet.seq, req);
            }
        }
    }

    @Override
    public void onConnConnect() {
        QLog.d(this, "onConnConnect");
        trySendingOne();
        if (pushCallback != null) {
            pushCallback.onConnConnect();
        }
    }


    @Override
    public void onRecvDu(final Packet resp) {
        mContinueFail = 0;
        dispatchResp(resp);
    }

    void dispatchResp(Packet resp) {
        long start = System.currentTimeMillis();
        log.log("recv " + Request.dumpPacket(resp));
        try {
            if (resp.ret == null) {
                return;
            }
            if (resp.ret == RetCode.PUSH_MSG.getValue()) {
                if (pushCallback != null) {
                    pushCallback.onPush(resp);
                }
                return;
            }
            if (resp.ret == RetCode.ERROR_NO_SESSION.getValue()) {
                shutdown();
                UserLogic.getInstance().logoutAndStart("kick");
                return;
            }
            if (waitForResps.containsKey(resp.seq)) {  // 应答
                Request req = waitForResps.remove(resp.seq);
                if (req.callback != null) {
                    handler.removeCallbacks(req.timeOutRun);
                    if (req.callback != null) {
                        req.callback.onResp(req, resp);
                    }
                }
            } else { // 或者是重复的包,或者是bug

            }
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - start;
            log.log("proc exception " + Request.dumpPacket(resp) + " cost " + cost, e);
        } finally {
            long cost = System.currentTimeMillis() - start;
            log.log("proc " + Request.dumpPacket(resp) + " cost " + cost);
        }

    }

    @Override
    public void onWriteDuFin() {
        mContinueFail = 0;
        trySendingOne();
    }

    // 有可能俩次进入,心跳和读写异常，duTransfer需要判断是否已经关过一次
    @Override
    public void onError(final Throwable e) {
        // 不再接收dutransfer的回调，就是要干掉它
        if (duTransfer != null) {
            duTransfer.callback = null;
            duTransfer = null;
            resetMap();
        }
        handler.removeCallbacks(reConnect);
        handler.postDelayed(reConnect, 5000);
        log.log("onError " + duTransfer + " callback:" + (duTransfer != null ? duTransfer.callback : null), e);
        if (pushCallback != null) {
            pushCallback.onConnDisconnect();
        }
    }

    Runnable reConnect = new Runnable() {
        @Override
        public void run() {
            log.log("reConnect isShutdown "+isShutdown);
            if (!isShutdown) {
                makeDuTransfer();
            }
        }
    };


    synchronized void makeDuTransfer() {
        handler.removeCallbacks(reConnect);
        duTransfer = new DUTransfer(serverIp, port, this, handler);
        log.log("makeDuTransfer " + duTransfer + " callback:" + (duTransfer != null ? duTransfer.callback : null));
    }

    synchronized void resetMap() {
        log.log("resetMap");
        pendingReqs.addAll(waitForResps.values());
        waitForResps.clear();
        Collections.sort(pendingReqs, new Comparator<Request>() {
            @Override
            public int compare(Request lhs, Request rhs) {
                if (lhs.packet.seq < rhs.packet.seq) {
                    return -1;
                } else if (lhs.packet.seq == rhs.packet.seq) {
                    return 0;
                } else {
                    return 1;
                }
            }
        });
    }
}
